package model

import (
	"database/sql"
	"errors"
	"fmt"
	"reflect"
	"strings"

	"gitee.com/githubdog/goci/libraries/clients/xmysql"
	"gitee.com/githubdog/goci/libraries/clients/xssh"
	"gitee.com/githubdog/goci/libraries/mylog"
	"gitee.com/githubdog/goci/system/db/qbuild"
	"gitee.com/githubdog/goci/tools/helper"
)

// Database ..
type Database struct {
	ConnType string
	TCPConf  xmysql.DBConf
	SSHConf  xssh.SSHConf
}

type ModelConf struct {
	ReadDbConf        Database
	WriteDbConf       Database
	ReadDbPause       bool
	WriteDbPause      bool
	StructTag         string
	LogDir            string
	LogFile           string
	DefaultLimitCount uint
	WriteLog          bool
}

type Mysql struct {
	tx           *sql.Tx
	readDb       *xmysql.DbGroup
	writeDb      *xmysql.DbGroup
	readDbPause  bool
	writeDbPause bool
	LogDir       string
	LogFile      string
}

// Model 模型
type Model struct {
	Mysql
	*qbuild.Query
	structPtr  []interface{}
	structKeys []string
	keepQuery  bool
	IngoreLog  bool
	logs       []string
}

type dbQueryLogData struct {
	Mark   string
	Query  string
	Params []interface{}
	Err    error
}

func (ld *dbQueryLogData) logDBQuerySql(m *Model) {
	if ld.Query != "" {
		if ld.Params != nil && len(ld.Params) > 0 {
			for _, v := range ld.Params {
				ld.Query = strings.Replace(ld.Query, "?", fmt.Sprintf("'%v'", v), 1)
			}
		}
		if ld.Err != nil { // && ld.Err != sql.ErrNoRows
			if ld.Mark != "" {
				ld.Mark = " " + ld.Mark + ":"
			}
			mylog.WriteLog("err", "", "==>>"+ld.Mark, ld.Err, "\nQuery:", ld.Query)
			ld.Query = "[error] " + ld.Query
		}
		m.logs = append(m.logs, ld.Query)
		if !m.IngoreLog && mConf.WriteLog {
			mylog.WriteLog(m.LogDir, m.LogFile, ld.Query)
		}
	}
}

func (m *Model) getStmt(q string) (*sql.Stmt, string, error) {
	var (
		err  error
		mark string
		stmt *sql.Stmt
	)
	if m.tx != nil {
		mark = "m.tx.Prepare"
		stmt, err = m.tx.Prepare(q)
	} else if db, e := m.GetReadDb(); e == nil {
		mark = "db.DbClient.Prepare"
		stmt, err = db.DbClient.Prepare(q)
	} else {
		err = e
	}

	return stmt, mark, err
}

func (m *Model) getRows(q string, params []interface{}) (*sql.Rows, string, error) {
	var (
		err  error
		mark string
		rows *sql.Rows
	)
	if m.tx != nil {
		mark = "m.tx.Query"
		rows, err = m.tx.Query(q, params...)
	} else if db, e := m.GetReadDb(); e == nil {
		mark = "db.DbClient.Query"
		rows, err = db.DbClient.Query(q, params...)
	} else {
		err = e
	}

	return rows, mark, err
}

func (m *Model) IsTransaction() bool {
	return m.tx == nil
}

func (m *Model) getRow(q string, params []interface{}) (*sql.Row, string, error) {
	var (
		err  error
		mark string
		row  *sql.Row
	)
	if m.tx != nil {
		mark = "m.tx.QueryRow"
		row = m.tx.QueryRow(q, params...)
	} else if db, e := m.GetReadDb(); e == nil {
		mark = "db.DbClient.QueryRow"
		row = db.DbClient.QueryRow(q, params...)
	} else {
		err = e
	}

	return row, mark, err
}

// ====================== 设置公共配置 start ======================
var (
	mConf         ModelConf
	commonReadDb  *xmysql.DbGroup
	commonWriteDb *xmysql.DbGroup
)

func SetConfig(c ModelConf) {
	if c.StructTag == "" {
		c.StructTag = "mysql"
	}
	mConf = c
	qbuild.SetConfig(qbuild.QueryConf{
		Prefix:       c.ReadDbConf.TCPConf.Prefix,
		DefaultLimit: mConf.DefaultLimitCount,
	})
}

// ====================== 设置公共配置 end ======================

// func (m *Model) MergeLog(m2 *Model) {
// 	m.logs = append(m.logs, m2.logs...)
// }

func (m *Model) OutputDBLog(uuid string) {
	if len(m.logs) > 0 {
		if uuid == "" {
			uuid = "-"
		}
		str := "================== " + uuid + " ==================\n" + strings.Join(m.logs, "\n")
		mylog.WriteLog(m.LogDir, m.LogFile, str)
	}
	m.logs = []string{}
}

// SetReadDbStatus ..
func (m *Model) SetReadDbStatus(status bool) {
	m.readDbPause = status
}

// SetWriteDbStatus ..
func (m *Model) SetWriteDbStatus(status bool) {
	m.writeDbPause = status
}

func (m *Model) ResetQuery() *Model {
	m.Query = qbuild.New()
	m.structPtr = nil
	m.keepQuery = false
	return m
}

func (m *Model) isClearQuery() {
	if !m.keepQuery {
		m.ResetQuery()
	} else {
		m.keepQuery = false
	}
}

func (m *Model) KeepQuery(keep bool) *Model {
	m.keepQuery = keep
	return m
}

func (m *Model) initReadDbConf(dbc Database) {
	m.readDb.TCP.Conf = dbc.TCPConf
	m.readDb.SSH.SSHConf = dbc.SSHConf
	m.readDb.ConnType = dbc.ConnType
	m.readDb.Name = "Read"
}

func (m *Model) initWriteDbConf(dbc Database) {
	m.writeDb.TCP.Conf = dbc.TCPConf
	m.writeDb.SSH.SSHConf = dbc.SSHConf
	m.writeDb.ConnType = dbc.ConnType
	m.writeDb.Name = "Write"
}

// New new model 使用共享数据库连接
func New() *Model {
	m := &Model{
		Mysql: Mysql{
			readDbPause:  mConf.ReadDbPause,
			writeDbPause: mConf.WriteDbPause,
			LogDir:       mConf.LogDir,
			LogFile:      mConf.LogFile,
		},
		Query: qbuild.New(),
	}
	if commonReadDb == nil {
		commonReadDb = new(xmysql.DbGroup)
	}
	if commonWriteDb == nil {
		commonWriteDb = new(xmysql.DbGroup)
	}
	m.Mysql.readDb = commonReadDb
	m.Mysql.writeDb = commonWriteDb
	m.initReadDbConf(mConf.ReadDbConf)
	m.initWriteDbConf(mConf.WriteDbConf)

	return m
}

// NewConn new model 使用新数据库连接
func NewConn() *Model {
	m := &Model{
		Mysql: Mysql{
			readDbPause:  mConf.ReadDbPause,
			writeDbPause: mConf.WriteDbPause,
			LogDir:       mConf.LogDir,
			LogFile:      mConf.LogFile,
		},
		Query: qbuild.New(),
	}
	m.Mysql.readDb = new(xmysql.DbGroup)
	m.Mysql.writeDb = new(xmysql.DbGroup)
	m.initReadDbConf(mConf.ReadDbConf)
	m.initWriteDbConf(mConf.WriteDbConf)

	return m
}

func CopyModel(m *Model) *Model {
	m2 := New()
	*m2 = *m
	return m2
}

// GetReadDb 获取读数据库
func (m *Model) GetReadDb() (xmysql.DbTCP, error) {
	var (
		ret xmysql.DbTCP
		err error
	)
	if m.readDbPause && m.writeDbPause {
		err = errors.New("read db status is stop")
	} else if m.readDbPause {
		ret, err = m.GetWriteDb()
	} else {
		ret, err = m.readDb.ConnMysql()
	}

	return ret, err
}

// GetWriteDb 获取写数据库
func (m *Model) GetWriteDb() (xmysql.DbTCP, error) {
	var (
		ret xmysql.DbTCP
		err error
	)
	if m.readDbPause && m.writeDbPause {
		err = errors.New("write db status is stop")
	} else if m.writeDbPause {
		ret, err = m.GetReadDb()
	} else {
		ret, err = m.writeDb.ConnMysql()
	}

	return ret, err
}

func (m *Model) GetOriginReadDb() *xmysql.DbGroup {
	return m.readDb
}

func (m *Model) GetOriginWriteDb() *xmysql.DbGroup {
	return m.writeDb
}

func (m *Model) GetLogs() []string {
	return m.logs
}

func (m *Model) CloseDb() {
	m.readDb.TCP.Close()
	m.writeDb.TCP.Close()
}

func (m *Model) CreateQuery(fn func(*qbuild.Query)) *Model {
	fn(m.Query)
	return m
}

// SetStructPtr 设置struct指针, 可拆解成qbuild.Query中的selects对应类型的values
// 那为什么不放在qbuild.Query包中呢
//  1. 需要用到 Model包中的数据
//  2. 多了一个error需要返回 (故也不拆解, 放在Model最后的函数处使用)
func (m *Model) SetStructPtr(args ...interface{}) *Model {
	// if len(args) > 0 {
	// 	m.structPtr = args[0]
	// } else if len(args) > 1 {
	// 	if v, ok := args[1].([]string); ok {
	// 		m.structKeys = v
	// 	}
	// }
	for _, arg := range args {
		if arg == nil {
			m.structPtr = []interface{}{}
		} else if _, ok := checkStructPtr(arg); ok {
			m.structPtr = append(m.structPtr, arg)
		} else if v, ok := arg.([]string); ok {
			m.structKeys = v
		}
	}
	return m
}

func (m *Model) HandleStructPtr() error {
	var (
		err    error
		fields []string
		values []interface{}
	)

	// if m.structPtr != nil {
	// 	// selects := m.GetSelects()
	// 	// if len(selects) == 0 {
	// 	if fields, values, err = GetFieldAndValue(m.structPtr, m.structKeys, mConf.StructTag); err == nil {
	// 		m.Select(fields...).Values(values...)
	// 	}
	// 	// }
	// }
	for _, v := range m.structPtr {
		if fields, values, err = GetFieldAndValue(v, m.structKeys, mConf.StructTag); err == nil {
			m.Select(fields...).Values(values...)
		} else {
			break
		}
	}
	return err
}

func checkStructPtr(v interface{}) (reflect.Type, bool) {
	var ret bool
	rType := reflect.TypeOf(v)
	if rType.Kind() == reflect.Ptr {
		rType = rType.Elem()
		if rType.Kind() == reflect.Struct {
			ret = true
		}
	}
	return rType, ret
}

func decodeMysqlTag(str string) string {
	str = strings.ReplaceAll(str, "~~", `"`)
	str = strings.ReplaceAll(str, "、", `,`)
	return str
}

// GetFieldAndValue 获取模型的所有字段与指针地址
// keys 为struct Name, 如: ID, Name | id, name
func GetFieldAndValue(v interface{}, keys []string, tagName string) (fields []string, vals []interface{}, err error) {
	fieldMap := map[string]bool{}
	if rType, ok := checkStructPtr(v); ok {
		vl := reflect.ValueOf(v).Elem()
		keyStr := fmt.Sprintf(",%s,", strings.Join(qbuild.FormatKeys(keys), ","))
		for i := 0; i < rType.NumField(); i++ {
			tag := helper.GetStructTag(rType.Field(i).Tag.Get(tagName))
			if tag != "" {
				tag = decodeMysqlTag(tag)
				_tag := tag
				tagSpit := strings.Split(tag, " ")
				if len(tagSpit) > 0 {
					_tag = tagSpit[len(tagSpit)-1]
				}
				if len(keys) == 0 ||
					strings.Contains(keyStr, fmt.Sprintf(",%s,", _tag)) ||
					strings.Contains(keyStr, fmt.Sprintf(",%s,", rType.Field(i).Name)) {
					fields = append(fields, tag)
					fieldMap[_tag] = true
					vals = append(vals, vl.Field(i).Addr().Interface())
				}
			}
		}
		// fields = strings.Join(formatArrayField(arr), ",")
		if len(fields) == 0 || len(vals) == 0 {
			err = errors.New("struct or field tag[" + tagName + "] is empty")
		} else if len(keys) > 0 {
			for _, v := range keys {
				if !fieldMap[v] {
					err = errors.New("selects field[" + v + "] is miss")
				}
			}
		}
	} else {
		err = errors.New("param type need struct pointer")
	}

	return
}

// ExecQuery 运行非SELECT的sql语句
func (m *Model) ExecQuery() (ret sql.Result, err error) {
	query := m.GetQueryString()
	params := m.Query.GetParams()
	ld := dbQueryLogData{
		Query:  query,
		Params: params,
	}
	if m.tx != nil {
		if ret, err = m.tx.Exec(query, params...); err != nil {
			ld.Mark = "ExecQuery m.tx.Exec"
		}
	} else {
		if db, e := m.GetWriteDb(); e == nil {
			if stmt, e := db.DbClient.Prepare(query); e == nil {
				defer stmt.Close()
				if ret, err = stmt.Exec(params...); err != nil {
					ld.Mark = "ExecQuery stmt.Exec"
				}
			} else {
				err = e
				ld.Mark = "ExecQuery db.DbClient.Prepare"
			}
		} else {
			err = e
		}
	}

	ld.Err = err
	ld.logDBQuerySql(m)
	m.isClearQuery()
	return
}

func (m *Model) GetInsertStr(data map[string]interface{}) (string, []interface{}) {
	keys := []string{}
	_vals := []string{}
	vals := []interface{}{}
	table := m.GetTableName()
	for k, v := range data {
		keys = append(keys, qbuild.FormatField(k))
		if _v, ok := v.(qbuild.RawSQLString); ok {
			_vals = append(_vals, string(_v))
		} else {
			_vals = append(_vals, "?")
			vals = append(vals, v)
		}
	}
	str := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s);", table, strings.Join(keys, ","), strings.Join(_vals, ","))
	return str, vals
}

// InsertInterfaceRow 添加一行非固定类型数据
func (m *Model) InsertInterfaceRow(table string, data map[string]interface{}) (rid int64, err error) {
	if table != "" {
		m.From(table)
	}
	str, vals := m.GetInsertStr(data)
	if len(vals) > 0 {
		m.SetQueryRaw(str).Params(vals...)
		if ret, e := m.ExecQuery(); e != nil {
			err = e
		} else {
			if rid, err = ret.LastInsertId(); err != nil {
				ld := dbQueryLogData{
					Query:  str,
					Params: vals,
					Mark:   "InsertInterfaceRow ret.LastInsertId",
					Err:    err,
				}
				ld.logDBQuerySql(m)
			}
		}
	}
	m.isClearQuery()
	return
}

func (m *Model) BatchInsert(table string, data BatchMysqlData, selects []string) (sql.Result, error) {
	var (
		ret  sql.Result
		err  error
		str  string
		vals []interface{}
	)

	if table != "" {
		m.From(table)
	}
	if str, vals, err = data.GetBatchInsertString(m.Query.GetTableName(), selects); err == nil {
		if wheres := m.GetWheres(); wheres != "" {
			str += " AND " + wheres
		}
		m.SetQueryRaw("INSERT" + str).Params(vals...)
		ret, err = m.ExecQuery()
		m.isClearQuery()
	}
	return ret, err
}

// BatchReplaceRow 批量插入数据
func (m *Model) BatchReplace(table string, data BatchMysqlData, selects []string) (sql.Result, error) {
	var (
		ret  sql.Result
		err  error
		str  string
		vals []interface{}
	)

	if table != "" {
		m.From(table)
	}
	if str, vals, err = data.GetBatchInsertString(m.Query.GetTableName(), selects); err == nil {
		if wheres := m.GetWheres(); wheres != "" {
			str += " AND " + wheres
		}
		m.SetQueryRaw("REPLACE" + str).Params(vals...)
		ret, err = m.ExecQuery()
		m.isClearQuery()
	}
	return ret, err
}

// Update 更新行数据
func (m *Model) Update(data map[string]interface{}) (num int64, err error) {
	keys := []string{}
	vals := []interface{}{}
	for k, v := range data {
		if v == nil {
			if strings.Contains(helper.Trim(k), " ") {
				keys = append(keys, k)
			} else {
				keys = append(keys, k+" = null")
			}
		} else if _v, ok := v.(qbuild.RawSQLString); ok {
			keys = append(keys, k+" = "+string(_v))
		} else {
			keys = append(keys, k+" = ?")
			vals = append(vals, v)
		}
	}
	if len(keys) > 0 {
		str := fmt.Sprintf("UPDATE %s SET %s", m.GetTableName(), strings.Join(keys, ","))
		if wheres := m.GetWheres(); wheres != "" {
			str += " WHERE " + wheres
		}
		m.SetQueryRaw(str).Params(vals...)
		if ret, e := m.ExecQuery(); e != nil {
			err = e
		} else {
			if num, err = ret.RowsAffected(); err != nil {
				ld := dbQueryLogData{
					Query:  str,
					Params: vals,
					Mark:   "Update",
					Err:    err,
				}
				ld.logDBQuerySql(m)
			}
		}
	}
	m.isClearQuery()
	return
}

func (m *Model) BatchUpdate(data BatchMysqlData, table string, args ...interface{}) (sql.Result, error) {
	var (
		ret     sql.Result
		err     error
		str     string
		selects []string
		vals    []interface{}
	)

	if table != "" {
		m.From(table)
	}
	primary := "id"
	if args != nil {
		if len(args) > 0 {
			if v, ok := args[0].(string); ok {
				primary = v
			}
			if len(args) > 1 {
				if v, ok := args[1].([]string); ok {
					selects = v
				}
			}
		}
	}
	if str, vals, err = data.GetBatchUpdateString(m.Query.GetTableName(), primary, selects); err == nil {
		if wheres := m.GetWheres(); wheres != "" {
			str += " AND " + wheres
		}
		m.SetQueryRaw(str).Params(vals...)
		ret, err = m.ExecQuery()
		m.isClearQuery()
	}
	return ret, err
}

func (m *Model) DeleteRow(table string, id int) (num int64, err error) {
	if table != "" {
		m.From(table)
	}
	m.WhereInt("id", id)
	str := fmt.Sprintf("DELETE FROM %s", m.GetTableName())
	if wheres := m.GetWheres(); wheres != "" {
		str += " WHERE " + wheres
	}
	str += " LIMIT 1"
	params := m.Query.GetParams()
	m.SetQueryRaw(str).Params(params...)
	if ret, e := m.ExecQuery(); e != nil {
		err = e
	} else {
		if num, err = ret.RowsAffected(); err != nil {
			ld := dbQueryLogData{
				Query:  str,
				Params: params,
				Mark:   "Delete",
				Err:    err,
			}
			ld.logDBQuerySql(m)
		}
	}
	m.isClearQuery()
	return
}

// RunTransaction 运行事务
func (m *Model) RunTransaction(handle func(tm *Model) (terr error)) (err error) {
	if db, e := m.GetWriteDb(); e == nil {
		tx, e := db.DbClient.Begin()
		if e != nil {
			err = e
			return e
		}

		defer func() {
			if err != nil {
				tx.Rollback()
			}
		}()

		m2 := New()
		m2.tx = tx
		if err = handle(m2); err != nil {
			return err
		}
		err = tx.Commit()
	} else {
		err = e
	}
	m.isClearQuery()
	return
}

// id 指针变量, eg: &id
func (m *Model) FindId(id interface{}) error {
	m.Query.Select("id").Values(id)
	return m.FindRow()
}

func (m *Model) CountBeforeClean() *Model {
	m.SetStructPtr(nil) //.KeepQuery(false)
	m.Query.Select().Values().CleanOrder().Limit(0).Offset(0)
	return m
}

func (m *Model) Count(args ...string) (num int, err error) {
	// 清除原有的条件
	m.CountBeforeClean()

	field := "*"
	if len(args) > 0 && args[0] != "" {
		field = args[0]
	}
	err = m.CreateQuery(func(q *qbuild.Query) {
		q.Values(&num).Select("count(" + field + ")")
	}).FindRow()
	return
}

// FindRow 查询单行
func (m *Model) FindRow() error {
	var (
		err error
		ld  dbQueryLogData
		row *sql.Row
	)

	if err = m.HandleStructPtr(); err == nil {
		ld.Query = m.GetQueryString()
		ld.Params = m.Query.GetParams()
		if row, ld.Mark, err = m.getRow(ld.Query, ld.Params); err == nil {
			if err = row.Scan(m.Query.GetValues()...); err != nil && err != sql.ErrNoRows {
				ld.Mark = "Scan"
			}
		}
	}

	ld.Err = err
	ld.Mark = "FindRow " + ld.Mark
	ld.logDBQuerySql(m)
	m.isClearQuery()
	return err
}

// FindRowPrepare 查询单行(预处理)
func (m *Model) FindRowPrepare() error {
	var (
		err  error
		ld   dbQueryLogData
		stmt *sql.Stmt
	)

	if err = m.HandleStructPtr(); err == nil {
		ld.Query = m.GetQueryString()
		ld.Params = m.Query.GetParams()
		if stmt, ld.Mark, err = m.getStmt(ld.Query); err == nil {
			defer stmt.Close()

			if err = stmt.QueryRow(ld.Params...).Scan(m.Query.GetValues()...); err != nil && err != sql.ErrNoRows {
				ld.Mark = "Scan"
			}
		}
	}

	ld.Err = err
	ld.Mark = "FindRowPrepare " + ld.Mark
	ld.logDBQuerySql(m)
	m.isClearQuery()
	return err
}

// QueryRows ..
func (m *Model) QueryRows(cb func()) error {
	var (
		err  error
		ld   dbQueryLogData
		rows *sql.Rows
	)

	if err = m.HandleStructPtr(); err == nil {
		ld.Query = m.GetQueryString()
		ld.Params = m.Query.GetParams()
		if rows, ld.Mark, err = m.getRows(ld.Query, ld.Params); err == nil {
			defer rows.Close()

			for rows.Next() {
				if err = rows.Scan(m.Query.GetValues()...); err == nil {
					cb()
				} else {
					ld.Mark = "rows.Scan"
					break
				}
			}
			if err == nil {
				if err = rows.Err(); err != nil {
					ld.Mark = "rows.Err"
				}
			}
		}
	}

	ld.Err = err
	ld.Mark = "QueryRows " + ld.Mark
	ld.logDBQuerySql(m)
	m.isClearQuery()
	return err
}

// QueryRowsPrepare 查询多行(预处理)
func (m *Model) QueryRowsPrepare(cb func()) error {
	var (
		err  error
		ld   dbQueryLogData
		stmt *sql.Stmt
	)

	if err = m.HandleStructPtr(); err == nil {
		ld.Query = m.GetQueryString()
		ld.Params = m.Query.GetParams()
		if stmt, ld.Mark, err = m.getStmt(ld.Query); err == nil {
			defer stmt.Close()

			if rows, e := stmt.Query(ld.Params...); e == nil {
				defer rows.Close()

				for rows.Next() {
					if err = rows.Scan(m.Query.GetValues()...); err == nil {
						cb()
					} else {
						ld.Mark = "rows.Scan"
						break
					}
				}
				if err == nil {
					if err = rows.Err(); err != nil {
						ld.Mark = "rows.Err"
					}
				}
			} else {
				err = e
				ld.Mark = "stmt.Query"
			}
		}
	}

	ld.Err = err
	ld.Mark = "QueryRowsPrepare " + ld.Mark
	ld.logDBQuerySql(m)
	m.isClearQuery()
	return err
}
